﻿@page "/"
@page "/c/{ChatId:guid}"

@inject IConfiguration _configuration
@inject ISnackbar Snackbar
@inject ChatState _chatState
@inject IChatService _chatService
@inject IActionService _actionService
@inject NavigationManager _navigationManager
@inject IJSRuntime JS
@inject TimeProvider _clock
@inject ISnackbar Snackbar

@implements IAsyncDisposable

<PageTitle>Home</PageTitle>

<div @ref="ChatMessagesDiv" class="d-flex flex-column overflow-auto flex-grow-1" style="height: 100vh">
    <div class="d-flex flex-column flex-grow-1 mt-4 @(_chatState.IsFullWidthText ? "mx-5 flex-grow-1" : "mx-auto")" style="@(_chatState.IsFullWidthText ? "" : "max-width: 960px; width: 100%;")">
        <div id="chat-messages" class="overflow-auto flex-grow-1">
            @if (_chatState.ChatId == Guid.Empty)
            {
                <div class="d-flex justify-center align-center h-100">
                    <NewChatPreview AssistantName="@_chatState.SelectedAssistant?.Name" UserName="@_chatState.UserName" AssistantAvatar="@_chatState.SelectedAssistant?.AvatarUrl" />
                </div>
            }
            else
            {
                @foreach (var chatMessage in ChatMessages)
                {
                    <ChatMessage Message=@chatMessage />
                }
            }
        </div>
    </div>
    <div style="position: sticky; bottom: 0;">
        <PromptInput OnSend="@SendMessageAsync" />
    </div>
</div>

@code
{
    [Parameter] public Guid? ChatId { get; set; }

    private List<ChatMessageResponse> ChatMessages { get; set; } = new();
    private ElementReference ChatMessagesDiv { get; set; } = new();
    private ChatMessageResponse LatestBotMessage { get; set; } = new();
    private StringBuilder _textStreaming = new();
    private HubConnection? _hubConnection;

    private const string ReceiveMessageUpdateServerCall = "ReceiveChatMessageChunk";
    private const string AddClientToGroupServerCall = "AddClientToGroupAsync";
    private string HubUrl => $"{_configuration["api:url"]}/hub";

    protected override async Task OnInitializedAsync()
    {
        _chatState.ChatIdChanged += async () => await LoadChatHistory();
        _chatState.SelectedAssistantChanged += StateHasChanged;
        _chatState.SelectedModelChanged += StateHasChanged;
        _chatState.ServiceIdChanged += StateHasChanged;
        _chatState.NativeActionServiceChanged += StateHasChanged;
        _chatState.UserNameChanged += StateHasChanged;
        _chatState.ActionIdChanged += StateHasChanged;
        _chatState.FullWidthTextChanged += StateHasChanged;

        _hubConnection = new HubConnectionBuilder()
            .WithUrl(HubUrl)
            .WithAutomaticReconnect()
            .Build();

        _hubConnection.On<string>(ReceiveMessageUpdateServerCall, async (message) =>
        {
            _textStreaming.Append(message);
            LatestBotMessage.Content = _textStreaming.ToString();

            await InvokeAsync(StateHasChanged);
            await ScrollToBottomAsync();

            Console.WriteLine($"ReceiveChatMessageChunk: {message}");
        });

        _hubConnection.On<string, RemoteActionStatus>(nameof(IChatHub.ReceiveProcessingStatus), (message, status) =>
        {
            var severity = status switch
            {
                RemoteActionStatus.Processing => Severity.Info,
                RemoteActionStatus.Success => Severity.Success,
                RemoteActionStatus.Failed => Severity.Error,
                _ => Severity.Info
            };

            ShowSnackbar(message, severity);
        });

        Console.WriteLine("Starting connection...");

        await _hubConnection.StartAsync();

        Console.WriteLine($"Connection state: {_hubConnection.State}");

        if (ChatId.HasValue && ChatId != Guid.Empty)
        {
            _chatState.SetChatId(ChatId.Value);
            await LoadChatHistory();
        }
    }

    private async Task LoadChatHistory()
    {
        var chatId = _chatState.ChatId;

        if (chatId != Guid.Empty)
        {
            var response = await _chatService.GetChatMessagesByChatId(_chatState.ChatId);
            var chatMessages = response.Value;

            ChatMessages = chatMessages.Items.ToList();

            await InvokeAsync(StateHasChanged);
            await ScrollToBottomAsync();
        }
    }

    private async Task SendMessageAsync(string message)
    {
        var chatId = _chatState.ChatId;
        var actionId = _chatState.ActionId;
        var assistantId = _chatState.SelectedAssistant!.Id;

        if (message.IsNotEmpty())
        {
            if (ShouldCreateNewChat(chatId))
            {
                chatId = await CreateNewChatAsync(assistantId, message);
            }

            await AppendMessagesToChatAsync(chatId, message);

            if (ShouldExecuteAction(actionId, chatId))
            {
                await ExecuteActionAsync(actionId, chatId, message);
                return;
            }

            await AskAssistant(chatId, message);
        }
    }

    private async Task<Guid> CreateNewChatAsync(Guid assistantId, string message)
    {
        var response = await _chatService.CreateChatSession(assistantId, "New Chat", message);

        if(!response.Succeeded)
        {
            ShowSnackbar("Failed to create a new chat. Please try again.", Severity.Error);
        }

        var newChatId = response.Value;

        _chatState.SetIsNewChatPending(true);
        _chatState.SetChatId(newChatId);

        await LoadChatHistory();
        _navigationManager.NavigateTo($"/c/{newChatId}");

        return newChatId;
    }

    private async Task AppendMessagesToChatAsync(Guid chatId, string message)
    {
        var userMessage = CreateUserMessage(chatId, message);
        LatestBotMessage = CreateBotMessage(chatId, string.Empty);

        ChatMessages.Add(userMessage);
        await ScrollToBottomAsync();

        ChatMessages.Add(LatestBotMessage);
        await ScrollToBottomAsync();
    }

    private async Task ExecuteActionAsync(Guid actionId, Guid chatId, string message)
    {
        await _actionService.ExecuteAction(actionId, chatId, _chatState.NativeActionServiceId, message);
        _textStreaming.Clear();
    }

    private async Task AskAssistant(Guid chatId, string message)
    {
        var response = await _chatService.SendMessage(chatId, message, "", _chatState.SelectedModel, _chatState.ServiceId);

        if(!response.Succeeded)
        {
            var error = response.Errors?.Errors?.FirstOrDefault();
            LatestBotMessage.Content = error?.UserMessage ?? "An unknown error occurred.";
        }

        _textStreaming.Clear();
    }

    private bool ShouldCreateNewChat(Guid chatId)
        => chatId == Guid.Empty;

    private bool ShouldExecuteAction(Guid actionId, Guid chatId)
        => actionId != Guid.Empty && chatId != Guid.Empty;

    private async Task ScrollToBottomAsync()
        => await JS.InvokeVoidAsync("scrollToBottom", ChatMessagesDiv);

    private ChatMessageResponse CreateUserMessage(Guid chatId, string message, ChatMessageType messageType = ChatMessageType.Message)
    {
        var now = _clock.GetLocalNow();

        var userMessage = new ChatMessageResponse
        {
            Id = Guid.NewGuid(),
            ChatId = chatId,
            Type = messageType,
            Author = "user",
            Content = message,
            Timestamp = now
        };

        return userMessage;
    }

    private ChatMessageResponse CreateBotMessage(Guid chatId, string message, ChatMessageType messageType = ChatMessageType.Message)
    {
        var now = _clock.GetLocalNow();

        var chatMessage = new ChatMessageResponse
        {
            Id = Guid.NewGuid(),
            ChatId = chatId,
            Type = messageType,
            Author = "bot",
            Content = message,
            Timestamp = now
        };

        return chatMessage;
    }

    private void ShowSnackbar(string message, Severity severity)
    {
        Snackbar.Configuration.PositionClass = Defaults.Classes.Position.TopRight;

        Snackbar.Add(message, severity, options =>
        {
            options.HideTransitionDuration = 100;
        });
    }

    public async ValueTask DisposeAsync()
    {
        _chatState.ChatIdChanged -= async () => await LoadChatHistory();
        _chatState.SelectedAssistantChanged -= StateHasChanged;
        _chatState.SelectedModelChanged -= StateHasChanged;
        _chatState.ServiceIdChanged -= StateHasChanged;
        _chatState.NativeActionServiceChanged -= StateHasChanged;
        _chatState.UserNameChanged -= StateHasChanged;
        _chatState.ActionIdChanged -= StateHasChanged;
        _chatState.FullWidthTextChanged -= StateHasChanged;

        if (_hubConnection is not null)
        {
            await _hubConnection.DisposeAsync();
        }
    }
}
